/*
 * TODO language 
 * Copyright (c) 2010 TODO Team
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <iostream>
#include <cstdlib>
#include "types.h"
#include "vm.h"
#include "internal.h"
#include "opcodes.h"
#include "API.h"
#include "gc.h"

namespace todo { namespace vm {

std::stack<Scope*> Engine::scopes;

Engine::Engine(const TodoModules& modules_) : exit_code(0), modules(modules_)
{
	// Global scope
	current_scope = global_scope = new Scope;
	scopes.push(global_scope);
}

Engine::~Engine()
{	
	// Deleting the scopes
	while (!scopes.empty()) {
		delete scopes.top();
		scopes.pop();
	}
}

/**
 * Fetches a variable from the current scope variables map
 * @param name the variable name
 */
inline todo::Data* Engine::get_var(const std::string& name)
{
	variable_map::iterator it =	scopes.top()->variables.find(name);
	
	if (it != scopes.top()->variables.end()) {
		return it->second->data;
	}
	throw todo::Exception("Variable `" + name + "' not found!");
} 

/**
 * TODO_PRINT
 */
void Engine::print(HANDLER_ARGS)
{
	tprintf("%v", get_value(op1));
}

/**
 * TODO_PRINTLN
 */
void Engine::println(HANDLER_ARGS)
{
	tprintlnf("%v", get_value(op1));
}

void Engine::add_func(HANDLER_ARGS)
{
	todo::Function* func = new todo::Function(STR_P(*op2->data), *op_num, op2->extra, (funcargs_t*)opcode->info);
	
	// Register the new function
	current_scope->functions.insert(std::pair<std::string, todo::Function*>(*func->name, func));

	// Skip its content
	JMP_TO(op2->extra);
}

inline void Engine::pop_scope(void)
{
	delete current_scope;
	scopes.pop();
	current_scope = scopes.top();
}

void Engine::end_func(HANDLER_ARGS)
{
	unsigned int jmp_calling = calling.top();
	
	calling.pop();
	pop_scope();

	JMP_TO(jmp_calling);
}

todo::Function* Engine::get_func(const std::string& name)
{
	todo::func_map::iterator it = global_scope->functions.find(name);
	
	// Try to find in the global scope
	it = global_scope->functions.find(name);
		
	if (it == global_scope->functions.end()) {
		throw todo::Exception("Cannot found function `" + name + "'");
	}
	
	return it->second;	
}

inline void Engine::args_push(ArgList* params, const funcargs_t* func_args)
{
	unsigned int i, params_count = params->args.size();
	
	for (i = 0 ; i < params_count; ++i) {
		if (params->args[i]->data_type == TODO_VAR) {
			register_var(new Var((*func_args)[i]->name, params->args[i]->data->type, get_var(STR_V(*params->args[i]->data))));
		} else if (params->args[i]->data_type == TODO_DYNAMIC) {
			Var* var;
			Data* result = new Data(TODO_CONSTANT);
			
			result->data = params->args[i]->result->value->type->copy(params->args[i]->result->value->addr);
			var = new Var((*func_args)[i]->name, params->args[i]->result->value->type, result);
			var->free = true;
			
			register_var(var);
		} else {
			register_var(new Var((*func_args)[i]->name, params->args[i]->data->type, params->args[i]));
		}
	}
}

void Engine::func_call(HANDLER_ARGS)
{
	try {
		todo::Function* func = get_func(STR_V(*op1->data));
	
		current_scope = new Scope;
	
		args_push((ArgList*)opcode->info, func->args);

		scopes.push(current_scope);
		calling.push(*op_num);
		returns.push(op1->result);

		JMP_TO(func->jmp_start);
	} catch (todo::Exception e) {
		modules_t::const_iterator it = modules.modules.find("Core");

		/* Trying to find in the core module */
		if (it != modules.modules.end()) {
			ArgValues* params;
			Module* mod = it->second;			
			module_funcs_t::iterator it2 = mod->functions.find(STR_V(*op1->data));
			
			if (it2 == mod->functions.end()) {
				throw todo::Exception("Cannot function `" + STR_V(*op1->data) + "'");
			}
			
			params = ((ArgList*)opcode->info)->get_values(this);
			
			op1->result->value = (mod->*(it2->second))(NULL, params);
			
			delete params;
		}
	}
}

/**
 * TODO_XOR
 * TODO_ASSIGN_XOR
 */
void Engine::xor_expr(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_XOR) {		
		op1->result->set_value(CALL_METHOD(op1_val, "__xor__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__xor__", &params);
		delete op1_val;
		//DESTROY(op1_val);
	}
}

/**
 * TODO_EXIT
 */
void Engine::exit_stmt(HANDLER_ARGS)
{
	if (op1->data) {
		exit_code = INT_V(*get_value(op1));
	}
	
	JMP_TO(-3);	
}

void Engine::return_stmt(HANDLER_ARGS)
{
	if (current_scope == global_scope) {
		// Just finish the execution
		JMP_TO(-2);
	} else {
		unsigned int jmp = calling.top();
		
		if (op1->data_type != todo::TODO_UNUSED) {
			GCValue* op1_val = get_value(op1);
			
			if (op1_val == NULL) {
				throw todo::Exception("Expecting return that not occurred!");
			}
			if (returns.top()->value) {
				delete returns.top()->value;
				// DESTROY(returns.top()->value);
			}			
			returns.top()->value = (*op1_val)->type->copy(op1_val->addr);
		}
		calling.pop();
		returns.pop();
		pop_scope();
		JMP_TO(jmp);
	}
}

/**
 * Fetches the value from a todo::Data
 */
inline GCValue* Engine::get_value(const Data* data)
{
	if (data->data_type == todo::TODO_VAR) {
		Data* var = get_var(STR_V(*data->data));
		
		return var->data_type == todo::TODO_DYNAMIC ? var->result->value : var->data;
	} else if (data->data_type == todo::TODO_CONSTANT) {
		return data->data;
	} else if (data->data_type == todo::TODO_DYNAMIC) {
		return RESULT_P(data);
	} else {
		throw todo::Exception("Opsss! No data type valid!");
	}
}

void Engine::new_vector(HANDLER_ARGS)
{
	RESULT_P(op1) = NEW("vector");
}

void Engine::vector_add(HANDLER_ARGS)
{
	VECTOR_P(*get_value(op1))->push_back(get_value(op2));
}

void Engine::new_map(HANDLER_ARGS)
{
	RESULT_P(op1) = NEW("map");
}

void Engine::map_add(HANDLER_ARGS)
{
	MAP_V(*RESULT_P(op1))[STR_V(get_value(op1)->addr)] = get_value(op2);
}

/** 
 * TODO_MINUS
 * TODO_ASSIGN_MINUS
 */
void Engine::minus(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_MINUS) {
		op1->result->set_value(CALL_METHOD(op1_val, "__minus__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__minus__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/** 
 * TODO_PLUS
 * TODO_ASSIGN_PLUS
 */
void Engine::plus(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));

	if (opcode->type == opcode::TODO_PLUS) {		
		op1->result->set_value(CALL_METHOD(op1_val, "__plus__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__plus__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/** 
 * TODO_DIV
 * TODO_ASSIGN_DIV
 */
void Engine::div(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_DIV) {		
		op1->result->set_value(CALL_METHOD(op1_val, "__div__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__div__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/** 
 * TODO_MULT
 * TODO_ASSIGN_MULT
 */
void Engine::mult(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_MULT) {		
		op1->result->set_value(CALL_METHOD(op1_val, "__mult__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__mult__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/** 
 * TODO_MOD
 * TODO_ASSIGN_MOD
 */
void Engine::mod(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_MOD) {
		op1->result->set_value(CALL_METHOD(op1_val, "__mod__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__mod__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/** 
 * TODO_SL
 * TODO_ASSIGN_SL
 */
void Engine::shift_left(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_SL) {
		op1->result->set_value(CALL_METHOD(op1_val, "__sl__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__sl__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/** 
 * TODO_SR
 * TODO_ASSIGN_SR
 */
void Engine::shift_right(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_SR) {
		op1->result->set_value(CALL_METHOD(op1_val, "__sr__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__sr__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}
}

/**
 * TODO_SLICE
 */
void Engine::slice(HANDLER_ARGS)
{
	GCValue* op1_val = get_value(op1);
	ArgValues* params = ((ArgList*)opcode->info)->get_values(this);
	
	op1->result->set_value(CALL_METHOD(op1_val, "__slice__", params));
	
	delete params;
}

/**
 * TODO_METHOD_CALL
 */
void Engine::method_call(HANDLER_ARGS)
{
	ArgValues* params = ((ArgList*)opcode->info)->get_values(this);
	
	// It's chaining call, we should delete the previous result value
	if (op1->data_type == todo::TODO_DYNAMIC) {
		op1->data = RESULT_P(op1);
	}	
	
	try {
		if (op1->data_type == todo::TODO_VAR) {
			todo::Data* var = get_var(STR_V(*op1->data));

			op1->result->set_value(CALL_METHOD(var->data, STR_V(*op2->data), params));
		} else {
			op1->result->set_value(CALL_METHOD(op1->data, STR_V(*op2->data), params));
		}
	} catch (todo::Exception e) {
		delete params;
		throw;
	}
	
	delete params;
}

/**
 * TODO_STATIC_METHOD_CALL
 */
void Engine::static_method_call(HANDLER_ARGS)
{
	ArgValues* arguments = ((ArgList*)opcode->info)->get_values(this);
	
	try {
		op1->result->set_value(CALL_STATIC_METHOD(op1, STR_V(*op2->data), arguments));
	} catch (todo::Exception e) {
		delete arguments;
		throw;
	}
	
	delete arguments;
}

/**
 * TODO_ASSIGN
 */
void Engine::assign(HANDLER_ARGS)
{
	Data* var;
	GCValue* old_value;
	GCValue* new_value = get_value(op2);
	
	var = get_var(STR_V(*op1->data));
	
	old_value = get_value(var);
	
	// Delete the old value
	if (old_value) {
		delete old_value;
		// DESTROY(old_value);
	}

	// Copying the new value
	var->data = (*new_value)->type->copy(new_value->addr);
}

/**
 * Registers a variable in the variables map
 * @param var the variable
 */
inline void Engine::register_var(Var* var)
{
	current_scope->variables.insert(std::pair<std::string, Var*>(var->name, var));
}

/**
 * TODO_DECLARE_VAR
 */
void Engine::declare_var(HANDLER_ARGS)
{
	if (op2->data_type == todo::TODO_VAR) {
		register_var(new Var(STR_V(*op1->data), op1->data->type, get_var(STR_V(*op2->data))));
	} else {
		register_var(new Var(STR_V(*op1->data), op1->data->type, op2));
	}
}

/**
 * TODO_PRE_INC
 */
void Engine::pre_increment(HANDLER_ARGS)
{
	Data* var = get_var(STR_V(*op1->data));
	
	op1->result->set_value(CALL_METHOD(var->data, "__pre_inc__", NULL));
}

/**
 * TODO_PRE_DEC
 */
void Engine::pre_decrement(HANDLER_ARGS)
{
	Data* var = get_var(STR_V(*op1->data));
	
	op1->result->set_value(CALL_METHOD(var->data, "__pre_dec__", NULL));
}

/**
 * TODO_POS_INC
 */
void Engine::pos_increment(HANDLER_ARGS)
{
	Data* var = get_var(STR_V(*op1->data));
	
	op1->result->set_value(CALL_METHOD(var->data, "__pos_inc__", NULL));
}

/**
 * TODO_POS_DEC
 */
void Engine::pos_decrement(HANDLER_ARGS)
{
	Data* var = get_var(STR_V(*op1->data));
	
	op1->result->set_value(CALL_METHOD(var->data, "__pos_dec__", NULL));
}

/**
 * TODO_JMPZ
 */
void Engine::if_cond(HANDLER_ARGS)
{	
	GCValue* val = get_value(op1);
	
	if (IS_FALSE(val)) {
		JMP_TO(op2->extra);
	}
}

/**
 * TODO_JMPZ_EX
 */
void Engine::jmpz_ex(HANDLER_ARGS)
{	
	GCValue* val = get_value(op1);
	
	// std::cout << "JMPZ_EX: " << op1->extra << " " << op2->extra << std::endl;
	
	if (IS_FALSE(val)) {
		// std::cout << "jmp2: " << op2->extra << std::endl;
		JMP_TO(op2->extra);
	}
	// std::cout << "jmp1: " << op1->extra << std::endl;
	JMP_TO(op1->extra);
}

/**
 * TODO_JMP
 */
void Engine::jmp(HANDLER_ARGS)
{
	// std::cout << "jmp: " << op1->extra << std::endl;
	JMP_TO(op1->extra);
}

/**
 * TODO_BW_OR
 * TODO_ASSIGN_BW_OR
 */
void Engine::bw_or_expr(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_BW_OR) {
		op1->result->set_value(CALL_METHOD(op1_val, "__bw_or__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__bw_or__", &params);
		// DESTROY(op1_val);
		delete op1_val;
	}	
}

/**
 * TODO_BW_AND
 * TODO_ASSIGN_BW_AND
 */
void Engine::bw_and_expr(HANDLER_ARGS)
{
	GCValue* op1_val;
	ArgValues params;
	
	op1_val = get_value(op1);
	params.args.push_back(get_value(op2));
		
	if (opcode->type == opcode::TODO_BW_AND) {
		op1->result->set_value(CALL_METHOD(op1_val, "__bw_and__", &params));
	} else {
		get_var(STR_V(*op1->data))->data = CALL_METHOD(op1_val, "__bw_and__", &params);
		delete op1_val;
		// DESTROY(op1_val);
	}	
}

/**
 * TODO_IS_NOT_EQUAL
 */
void Engine::is_not_equal(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__not_equal__", &params));
	
	params.args.clear();
}

/**
 * TODO_IS_EQUAL
 */
void Engine::is_equal(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__equal__", &params));
	
	params.args.clear();
}

/**
 * TODO_GREATER
 */
void Engine::greater(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__gt__", &params));
	
	params.args.clear();
}

/**
 * TODO_LESS
 */
void Engine::less(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__lt__", &params));
	
	params.args.clear();
}

/**
 * TODO_GREATER_EQUAL
 */
void Engine::greater_equal(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__ge__", &params));
	
	params.args.clear();
}

/**
 * TODO_LESS_EQUAL
 */
void Engine::less_equal(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__le__", &params));
	
	params.args.clear();
}

/**
 * TODO_SUBSCRIPT
 */
void Engine::subscript(HANDLER_ARGS)
{
	ArgValues params;
	GCValue* val = get_value(op1);
	
	params.args.push_back(get_value(op2));
	
	op1->result->set_value(CALL_METHOD(val, "__subscript__", &params));
	
	params.args.clear();
}

/**
 * Executes the opcodes
 * @param opcode_list the opcode list
 */
void Engine::run(std::vector<todo::opcode::Opcode*>& opcode_list)
{
	long i, list_size = opcode_list.size();
	todo::opcode::Opcode* opcode;

	try {
		for (i = 0; i >= 0 && i < list_size; ++i) {
			 opcode = opcode_list[i];
			 //opcode_list[i]->dump();
			if (!opcode->handler) {
				throw todo::Exception("Missing handler for opcode");
			}
			(this->*(opcode->handler))(&i, &opcode->op1, &opcode->op2, opcode);
		}

		/* Exit */
		if (i == -2) {
			shutdown(opcode_list);
			exit(exit_code);
		}
	} catch (todo::Exception e) {
		std::cout << "Fatal error: " << e.what() << std::endl;
		exit(1);
	}
}

/**
 * Deletes all opcodes
 * @param opcode_list the opcode list
 */
void Engine::shutdown(std::vector<todo::opcode::Opcode*>& opcode_list)
{
	unsigned int i, list_size = opcode_list.size();
	
	for (i = 0; i < list_size; ++i) {
		// opcode_list[i]->dump();
		
		// Delete the opcode
		delete opcode_list[i];
	}	
	
	opcode_list.clear();
}

}} // todo::vm
